#include <basic/errcode.h>
#include <basic/datetime.h>
#include <basic/ossl.h>
#include <basic/dbc.h>
#include <basic/pgc.h>

#include <libpq-fe.h>

#define EXPAND_ROWS         10

/*----- 多线程 -----*/
struct _pgc_conn
{/*{{{*/
    PGconn *conn;
};/*}}}*/

static struct _pgc_conn pgc_conn[DBC_CONN_MAX_NUM];
#define CONN pgc_conn[conn_index]

static char *errcode_list[]=
{/*{{{*/
    "00000", "01000", "0100C", "01008", "01003",
    "01004", "02000", "02001", "03000", "08000",
    "08003", "08006", "08001", "08004", "08007",
    "08P01", "09000", "0A000", "0B000", "0F000",
    "0F001", "0L000", "0LP01", "0P000", "21000",
    "22000", "2202E", "22021", "22008", "22012",
    "22005", "2200B", "22022", "22015", "22018",
    "22007", "22019", "2200D", "22025", "22010",
    "22020", "22023", "2201B", "22009", "2200C",
    "2200G", "22004", "22002", "22003", "22026",
    "22001", "22011", "22027", "22024", "2200F",
    "22P01", "22P02", "22P03", "22P04", "22P05",
    "23000", "23001", "23502", "23503", "23505",
    "23514", "24000", "25000", "25001", "25002",
    "25008", "25003", "25004", "25005", "25006",
    "25007", "25P01", "25P02", "26000", "27000",
    "28000", "2B000", "2BP01", "2D000", "2F000",
    "2F005", "2F002", "2F003", "2F004", "34000",
    "38000", "38001", "38002", "38003", "38004",
    "39000", "39001", "39004", "39P01", "39P02",
    "3D000", "3F000", "40000", "40002", "40001",
    "40003", "40P01", "42000", "42601", "42501",
    "42846", "42803", "42830", "42602", "42622",
    "42939", "42804", "42P18", "42809", "42703",
    "42883", "42P01", "42P02", "42704", "42701",
    "42P03", "42P04", "42723", "42P05", "42P06",
    "42P07", "42712", "42710", "42702", "42725",
    "42P08", "42P09", "42P10", "42611", "42P11",
    "42P12", "42P13", "42P14", "42P15", "42P16",
    "42P17", "44000", "53000", "53100", "53200",
    "53300", "54000", "54001", "54011", "54023",
    "55000", "55006", "55P02", "57000", "57014",
    "57P01", "57P02", "57P03", "58030", "58P01",
    "58P02", "F0000", "F0001", "XX000", "XX001",
    "XX002", NULL
};/*}}}*/

static int _get_errcode(char *orig_code)
{/*{{{*/
    if(orig_code)
    {
        int i;
        for(i=0; errcode_list[i]; i++)
        {
            if(strcmp(orig_code, errcode_list[i]) == 0)
                return(i);
            i++;
        }
        return(-1);
    }
    return(PG_ERROR);    
}/*}}}*/

int pgc_init()
{/*{{{*/
    int conn_index;

    ossl_init_mt();
    for(conn_index=0; conn_index<DBC_CONN_MAX_NUM; conn_index++)
        CONN.conn=NULL;
    return(0);
}/*}}}*/

int pgc_cleanup()
{/*{{{*/
    ossl_clean_mt();
    return(0);
}/*}}}*/

int pgc_connect_ex(int conn_index,
        char *dbuser, char *dbpasswd, char *database, char *host)
{/*{{{*/
    int len;

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
            dbuser == NULL ||
            (len=strlen(dbuser)) <= 0 || len > NAME_SIZE ||
            dbpasswd == NULL ||
            (len=strlen(dbpasswd)) < PASSWD_MIN_SIZE ||
            len > PASSWD_MAX_SIZE ||
            database == NULL ||
            (len=strlen(database)) <= 0 || len > NAME_SIZE ||
            (host && ((len=strlen(host)) <= 0 || len > NAME_SIZE)))
        return(PG_ERROR);

    CONN.conn=PQsetdbLogin(host, NULL, NULL, NULL,
        database, dbuser, dbpasswd);
    if(PQstatus(CONN.conn) != CONNECTION_OK)
    {
        PQfinish(CONN.conn);
        return(PG_ERROR);
    }

    return(PG_OK);
}/*}}}*/

int pgc_disconnect_ex(int conn_index)
{/*{{{*/
    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1)
    {
        return(PG_ERROR);
    }

    PQfinish(CONN.conn);
    return(PG_OK);
}/*}}}*/

int pgc_begin_ex(int conn_index)
{/*{{{*/
    PGresult *res;
    
    res = PQexec(CONN.conn, "BEGIN");
    if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
        return(PG_ERROR);
    PQclear(res); 
    return(PG_OK);  
}/*}}}*/

int pgc_commit_ex(int conn_index)
{/*{{{*/
    PGresult *res;
    
    res = PQexec(CONN.conn, "COMMIT");
    if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
        return(PG_ERROR);
    PQclear(res);
    return(PG_OK);
}/*}}}*/

int pgc_rollback_ex(int conn_index)
{/*{{{*/
    PGresult *res;
    
    res = PQexec(CONN.conn, "ROLLBACK");
    if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
        return(PG_ERROR);
    PQclear(res); 
    return(PG_OK);
}/*}}}*/

int pgc_exec_ex(int conn_index, char *sql)
{/*{{{*/
    PGresult *res;
    char *errcode;

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
        sql == NULL)
        return(PG_ERROR);
    
    res=PQexec(CONN.conn, sql);
    if(res == NULL || PGRES_COMMAND_OK != PQresultStatus(res))
    {
        errcode=PQresultErrorField(res, PG_DIAG_SQLSTATE);
        PQclear(res);
        return(_get_errcode(errcode));
    }
    errcode=PQcmdTuples(res);
    PQclear(res);
    if(atoi(errcode) == 0)
        return(PG_NO_DATA_FOUND);
    return(PG_OK);
}/*}}}*/

int pgc_query_ex(int conn_index, char *sql, int request_row_num,
    struct dbc_query_result_set *result_set)
{/*{{{*/
    PGresult *res;
    int row_index, col_index;
    char *errcode=NULL;

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
        sql == NULL || result_set == NULL)
        return(PG_ERROR);

    // 初始化查询结果集
    result_set->rows=NULL;
    result_set->col_num=0;
    result_set->row_num=0;
    result_set->row_alloc_num=0;
    result_set->query_flag=-1;

    // 查询
    if((res=PQexec(CONN.conn, sql)) == NULL)
        goto ERROR_SELECT;
    if(PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        errcode=PQresultErrorField(res, PG_DIAG_SQLSTATE);
        goto ERROR_SELECT;
    }
    result_set->col_num=PQnfields(res); 
    
    // 查询结果集分配空间
    if(request_row_num > 0)
    {
        ALLOC_ARRAY(result_set->rows, char **, request_row_num,
                result_set->row_alloc_num, ERROR_SELECT);
        for(row_index=0; row_index<request_row_num; row_index++)
            ROW=NULL;
    }

    while(1)
    {
        if(PQntuples(res) == result_set->row_num)
            break;
        result_set->row_num++;
        if(request_row_num > 0 &&
                result_set->row_num == request_row_num+1)
            break;
    
        // 扩展行
        if(result_set->row_num > result_set->row_alloc_num)
        {
            EXPAND_ARRAY(result_set->rows, char **, EXPAND_ROWS,
                    result_set->row_alloc_num, ERROR_SELECT);
            for(row_index=result_set->row_num-1; row_index<EXPAND_ROWS;
                row_index++)
                ROW=NULL;
        }
        // 行
        row_index=result_set->row_num-1;
        ALLOC_MULTI(ROW, char *, result_set->col_num, ERROR_SELECT);
        for(col_index=0; col_index<result_set->col_num; col_index++)
        {
            // 列
            ALLOC_MULTI(COL, char,
                PQgetlength(res, row_index, col_index)+1,
                ERROR_SELECT);
            strcpy(COL, PQgetvalue(res, row_index, col_index));
        }
    }
    
    /*----- 释放pg结果集 -----*/
    PQclear(res);
 
    /*----- 无记录 -----*/
    if(result_set->row_num == 0)
    {
    	dbc_query_free(result_set);
		return(PG_NO_DATA_FOUND);
    }
    /*----- 查询位置 -----*/
    if(request_row_num > 0 &&
        result_set->row_num == request_row_num+1)
    {
        result_set->query_flag=QUERY_HALFWAY;
        result_set->row_num=request_row_num;
    }
    else
        result_set->query_flag=QUERY_BOTTOM;

    return(PG_OK);

ERROR_SELECT:
    PQclear(res);
    dbc_query_free(result_set);
    return(_get_errcode(errcode));
}/*}}}*/

int pgc_sequence_nextval_ex(int conn_index, char *seq, long* nextval)
{/*{{{*/
    char sql[200];
    int result;
    struct dbc_query_result_set result_set; 

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
        seq == NULL || nextval == NULL)
        return(PG_ERROR);

    sprintf(sql, "select nextval ('%s')", seq);
    result=pgc_query_ex(conn_index, sql, 1, &result_set);
    if(result != PG_OK)
        return(result);
    if(result_set.row_num == 0)
        return(PG_ERROR);
    trim_tail_space_notnull(result_set.rows[0][0]);
    *nextval=atol(result_set.rows[0][0]);
    dbc_query_free(&result_set);

    return(PG_OK);
}/*}}}*/
